package com.sluggard.workflow.controller;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.impl.persistence.entity.DeploymentEntity;
import org.camunda.bpm.engine.impl.persistence.entity.SuspensionState;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.repository.ResourceDefinition;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sluggard.common.exception.BusinessException;
import com.sluggard.common.model.res.Result;
import com.sluggard.workflow.entity.res.DeploymentResponse;
import com.sluggard.workflow.entity.res.ProcessDefinitionResponse;
import com.sluggard.workflow.model.dto.ProcessDefinitionStateDTO;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Tag(name = "工作流-流程定义管理")
@RestController
@RequestMapping("workflow/repository/processDefinition")
public class RepositoryController {

    @Resource
    private RepositoryService repositoryService;

    @Operation(summary = "列表查询")
    @GetMapping("list")
    public Result<Page<ProcessDefinitionResponse>> list(@RequestParam("pageNo") Integer pageNo,
            @RequestParam("pageSize") Integer pageSize,
            @RequestParam(value = "key", required = false) String key,
            @RequestParam(value = "status", required = false) Integer status,
            @RequestParam(value = "name", required = false) String name) {
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
                .orderByProcessDefinitionKey()
                .desc()
                .orderByProcessDefinitionVersion()
                .desc()
                .list()
                .parallelStream()
                .filter(item -> {
                    if (Objects.isNull(status)) {
                        return true;
                    }
                    if (status == 1) {
                        return !item.isSuspended();
                    }
                    return item.isSuspended();
                })
                .filter(item -> {
                    if (StringUtils.isBlank(name) && StringUtils.isBlank(key)) {
                        return true;
                    }
                    return StringUtils.contains(item.getName(), name) || StringUtils.contains(item.getKey(), key);
                })
                .toList();

        Page<ProcessDefinitionResponse> page = new Page<>();
        if (processDefinitions.isEmpty()) {
            return Result.ok(page);
        }

        int pageTotal = (processDefinitions.size() + pageSize - 1) / pageSize;
        if (pageNo > pageTotal) {
            pageNo = pageTotal;
        }
        int start = (pageNo - 1) * pageSize;
        int end = start + pageSize;
        if (end >= processDefinitions.size()) {
            end = processDefinitions.size();
        }

        page.setRecords(processDefinitions.subList(start, end).parallelStream()
                .map(item -> {
                    ProcessDefinitionResponse processDefinitionResponse = new ProcessDefinitionResponse();
                    BeanUtils.copyProperties(item, processDefinitionResponse);
                    List<ProcessDefinitionResponse> list = repositoryService.createProcessDefinitionQuery()
                            .processDefinitionKey(item.getKey())
                            .list()
                            .stream()
                            .sorted(Comparator.comparingInt(ResourceDefinition::getVersion).reversed())
                            .map(processDefinition -> {
                                ProcessDefinitionResponse response = new ProcessDefinitionResponse();
                                BeanUtils.copyProperties(processDefinition, response);
                                return response;
                            })
                            .toList();
                    if (StringUtils.isNotBlank(item.getDeploymentId())) {
                        processDefinitionResponse
                                .setDeploymentResponse(DeploymentResponse.from((DeploymentEntity) repositoryService
                                        .createDeploymentQuery()
                                        .deploymentId(item.getDeploymentId())
                                        .singleResult()));
                    }
                    processDefinitionResponse.setHistoryList(list);
                    return processDefinitionResponse;
                })
                .toList());
        page.setCurrent(pageNo);
        page.setTotal(processDefinitions.size());
        page.setPages(pageTotal);
        return Result.ok(page);
    }

    @Operation(summary = "下拉列表查询")
    @GetMapping("options")
    public Result<List<ProcessDefinitionResponse>> options(
            @RequestParam(value = "name", required = false) String name) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotBlank(name)) {
            processDefinitionQuery.processDefinitionNameLike("%" + name + "%");
        }

        return Result.ok(processDefinitionQuery
                .active()
                .list().stream().map(item -> {
                    ProcessDefinitionResponse processDefinitionResponse = new ProcessDefinitionResponse();
                    BeanUtils.copyProperties(item, processDefinitionResponse);
                    if (StringUtils.isNotBlank(item.getDeploymentId())) {
                        processDefinitionResponse
                                .setDeploymentResponse(DeploymentResponse.from((DeploymentEntity) repositoryService
                                        .createDeploymentQuery()
                                        .deploymentId(item.getDeploymentId())
                                        .singleResult()));
                    }
                    return processDefinitionResponse;
                })
                .toList());
    }

    @Operation(summary = "获取BPMN文件")
    @GetMapping("bpmn")
    public Result<String> bpmn(@RequestParam String processDefinitionKey) {
        BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(processDefinitionKey);
        if (Objects.isNull(modelInstance)) {
            throw new BusinessException("文件不存在");
        }
        Result<String> ok = Result.ok();
        ok.setData(Bpmn.convertToString(modelInstance));
        return ok;
    }

    @Operation(summary = "创建流程定义")
    @PostMapping("/deployment/create")
    public Result<String> create(@RequestParam String deploymentSource,
            @RequestParam String deploymentName,
            @RequestParam Boolean enableDuplicateFiltering,
            @RequestParam MultipartFile data) {
        try {
            Deployment deployment = repositoryService.createDeployment()
                    .source(deploymentSource)
                    .addInputStream(deploymentName + ".bpmn", data.getInputStream())
                    .enableDuplicateFiltering(enableDuplicateFiltering)
                    .deploy();

            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deployment.getId())
                    .singleResult();
            suspendOtherProcessDefinition(processDefinition.getId());
        } catch (Exception e) {
            log.error("创建流程定义失败", e);
            throw new BusinessException("创建流程定义失败");
        }
        return Result.ok();
    }

    @Operation(summary = "修改流程状态")
    @PutMapping("/state")
    public Result<Void> suspendProcessDefinition(@RequestBody ProcessDefinitionStateDTO request) {
        if (request.getSuspensionState() == SuspensionState.ACTIVE.getStateCode()) {
            repositoryService.activateProcessDefinitionById(request.getProcessDefinitionId());
            suspendOtherProcessDefinition(request.getProcessDefinitionId());
        } else {
            repositoryService.suspendProcessDefinitionById(request.getProcessDefinitionId());
        }
        return Result.ok();
    }

    private void suspendOtherProcessDefinition(String processDefinitionId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId)
                .singleResult();

        repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(processDefinition.getKey())
                .list()
                .stream()
                .filter(item -> !item.getId().equals(processDefinitionId))
                .forEach(item -> {
                    repositoryService.suspendProcessDefinitionById(item.getId());
                });
    }

    @Operation(summary = "删除流程定义")
    @DeleteMapping
    public Result<String> deleteDeployment(@RequestParam("processDefinitionIds") List<String> processDefinitionIds) {
        if (CollectionUtils.isEmpty(processDefinitionIds)) {
            return Result.error("参数错误 processDefinitionIds 不能为空");
        }
        processDefinitionIds.forEach(item -> repositoryService.deleteProcessDefinition(item, true));
        return Result.ok();
    }
}
